﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;

namespace HWEngine
{
	public static class LineAlgorithms
	{
		public static PointF GetMiddlePoint(PointF p1, PointF p2)
		{
			return new PointF((p1.X + p2.X)/2, (p1.Y + p2.Y)/2);
		}

		public static PointF GetNewPointCoordinates(this Line line, Func<int, int, Color> graphicsWrapper, SizeF graphicsSize)
		{
			//automation algorithm
			//инициализация начального вектора
			int size = line.Points.Count;
			PointF p1 = line.Points[size - 2];
			PointF p2 = line.Points[size - 1];

			//вычисление угла по координатам(по начальному вектору)
			double alfa = (float)Math.Atan((p2.X - p1.X) / (p2.Y - p1.Y + 0.0001));
			if (p2.Y < p1.Y) alfa -= Math.PI;

			//вычисление длины
			double d = Math.Sqrt(Sqr(p2.X - p1.X) + Sqr(p2.Y - p1.Y));

			//запоминание всех окружающих точек в массиве
			List<uint> colors = new List<uint>();
			List<PointF> points = new List<PointF>();
			PointF p3 = new PointF();
			Color col;
			uint midcolor = 0;
			for (double t = alfa - Math.PI; t < alfa + Math.PI; t += 0.1)
			{
				p3.X = (float)(p2.X + d * Math.Sin(t));
				p3.Y = (float)(p2.Y + d * Math.Cos(t));
				if (p3.X <= 0 || p3.X >= graphicsSize.Width || p3.Y <= 0 || p3.Y >= graphicsSize.Height)
				{
					col = Color.White;
				}
				else
				{
					col = graphicsWrapper((int) p3.X, (int) p3.Y);
				}
				uint value = (uint)(col.R + col.G + col.B);
				midcolor += value;
				colors.Add(value);
				points.Add(p3);
			}
			midcolor /= (uint)colors.Count;

			//проход массива цветов
			bool inGroup = false;
			int groupStartIndex = 0;
			List<int> groups = new List<int>();
			int bestGroupIndex = -1;
			int bestDistance = points.Count;
			for (int t = 0; t < points.Count; t++)
			{
				if (!inGroup)
				{
					if (colors[t] < midcolor) //вход в группу
					{
						inGroup = true;
						groupStartIndex = t;
					}
				}
				else
				{
					if (colors[t] > midcolor) //выход из группы
					{
						inGroup = false;
						int groupCenter = (t + groupStartIndex) / 2;
						groups.Add(groupCenter);
						int distance = Math.Abs(points.Count / 2 - groupCenter);
						if (distance < bestDistance)
						{
							bestGroupIndex = groups.Count - 1;
							bestDistance = distance;
						}
					}
				}
			}

			//определение нужной группы
			if (groups.Count > 0)
			{
				p3 = points[groups[bestGroupIndex]];
			}
			else
			{
				p3 = points[points.Count / 2];
			}

			return p3;
		}

		public static IEnumerable<PointF> CalculateSpline(this Line line)
		{
			List<PointF> result = new List<PointF>(10);
			var points = line.Points;
			var tension = line.Tension;
			if (points.Count == 2)
			{
				result.Add(points[0]);
				result.Add(points[1]);
				return result;
			}
			
			//////////////////////////////
			//   Первый отрезок кривой  //
			//////////////////////////////
			float step = 0.1f;
			float x0 = points[0].X;
			float y0 = points[0].Y;
			float x1 = points[1].X;
			float y1 = points[1].Y;
			float t1x = ((points[2].X - points[0].X)*tension);
			float t1y = ((points[2].Y - points[0].Y)*tension);
			float t = 0;

			while (t <= 1.001)
			{
				float x = (x0*(1 - t)*(1 - t)*(1 - t) + x1*t*t*t + (3*x1 - t1x)*t*t*(1 - t) +
				           (3*x0 + 3*x1 - t1x)*(1 - t)*(1 - t)/2*t);
				float y = (y0*(1 - t)*(1 - t)*(1 - t) + y1*t*t*t + (3*y1 - t1y)*t*t*(1 - t) +
				           (3*y0 + 3*y1 - t1y)*(1 - t)*(1 - t)/2*t);
				result.Add(new PointF(x, y));
				t += step;
			}

			/////////////////////////////////
			//  Все промежуточные отрезки  //
			/////////////////////////////////
			int i = 1;
			while (i < points.Count - 2)
			{
				x0 = points[i].X;
				y0 = points[i].Y;
				x1 = points[i + 1].X;
				y1 = points[i + 1].Y;
				t1x = ((points[i + 1].X - points[i - 1].X)*tension);
				t1y = ((points[i + 1].Y - points[i - 1].Y)*tension);
				float t2x = ((points[i + 2].X - points[i].X)*tension);
				float t2y = ((points[i + 2].Y - points[i].Y)*tension);
				t = 0;
				while (t <= 1.001)
				{
					float x = (x0*(1 - t)*(1 - t)*(1 - t) + x1*t*t*t + (3*x0 + t1x)*t*(1 - t)*(1 - t) + (3*x1 - t2x)*t*t*(1 - t));
					float y = (y0*(1 - t)*(1 - t)*(1 - t) + y1*t*t*t + (3*y0 + t1y)*t*(1 - t)*(1 - t) + (3*y1 - t2y)*t*t*(1 - t));
					result.Add(new PointF(x, y));
					t += step;
				}
				i++;
			}

			//////////////////////////
			//   Последний отрезок  //
			//////////////////////////
			x0 = points[i].X;
			y0 = points[i].Y;
			x1 = points[i + 1].X;
			y1 = points[i + 1].Y;
			t1x = ((points[i + 1].X - points[i - 1].X)*tension);
			t1y = ((points[i + 1].Y - points[i - 1].Y)*tension);
			t = 0;
			while (t <= 1.001)
			{
				float x = (x0*(1 - t)*(1 - t)*(1 - t) + x1*t*t*t + (3*x0 + t1x)*t*(1 - t)*(1 - t) +
				           (3*x0 + 3*x1 + t1x)*(1 - t)*t/2*t);
				float y = (y0*(1 - t)*(1 - t)*(1 - t) + y1*t*t*t + (3*y0 + t1y)*t*(1 - t)*(1 - t) +
				           (3*y0 + 3*y1 + t1y)*(1 - t)*t/2*t);
				result.Add(new PointF(x, y));
				t += step;
			}
			return result;
		}

		private static float Sqr(float a)
		{
			return a * a;
		}

		public static IEnumerable<PointF> CalculateSpline2(this Line line)
		{
			return CreateSpline(line.Points, line.Tension, null, false, 0.1);
		}

		internal static IList<PointF> CreateSpline(IList<PointF> pts, double tension, IList<double> tensions,
												  bool isClosed, double tolerance)
		{
			if (pts == null || pts.Count < 1)
				return null;

			IList<PointF> polyLineSegment = new List<PointF>();

			if (pts.Count < 2)
				return polyLineSegment;

			else if (pts.Count == 2)
			{
				if (!isClosed)
				{
					Segment(polyLineSegment, pts[0], pts[0], pts[1], pts[1], tension, tension, tolerance);
				}
				else
				{
					Segment(polyLineSegment, pts[1], pts[0], pts[1], pts[0], tension, tension, tolerance);
					Segment(polyLineSegment, pts[0], pts[1], pts[0], pts[1], tension, tension, tolerance);
				}
			}
			else
			{
				bool useTensionCollection = tensions != null && tensions.Count > 0;

				for (int i = 0; i < pts.Count; i++)
				{
					double T1 = useTensionCollection ? tensions[i % tensions.Count] : tension;
					double T2 = useTensionCollection ? tensions[(i + 1) % tensions.Count] : tension;

					if (i == 0)
					{
						Segment(polyLineSegment, isClosed ? pts[pts.Count - 1] : pts[0],
														pts[0], pts[1], pts[2], T1, T2, tolerance);
					}

					else if (i == pts.Count - 2)
					{
						Segment(polyLineSegment, pts[i - 1], pts[i], pts[i + 1],
														isClosed ? pts[0] : pts[i + 1], T1, T2, tolerance);
					}

					else if (i == pts.Count - 1)
					{
						if (isClosed)
						{
							Segment(polyLineSegment, pts[i - 1], pts[i], pts[0], pts[1], T1, T2, tolerance);
						}
					}

					else
					{
						Segment(polyLineSegment, pts[i - 1], pts[i], pts[i + 1], pts[i + 2], T1, T2, tolerance);
					}
				}
			}

			return polyLineSegment;
		}

		static void Segment(ICollection<PointF> points, PointF pt0, PointF pt1, PointF pt2, PointF pt3, double T1, double T2, double tolerance)
		{
			// See Petzold, "Programming Microsoft Windows with C#", pages 645-646 or 
			//     Petzold, "Programming Microsoft Windows with Microsoft Visual Basic .NET", pages 638-639
			// for derivation of the following formulas:

			double SX1 = T1 * (pt2.X - pt0.X);
			double SY1 = T1 * (pt2.Y - pt0.Y);
			double SX2 = T2 * (pt3.X - pt1.X);
			double SY2 = T2 * (pt3.Y - pt1.Y);

			double AX = SX1 + SX2 + 2 * pt1.X - 2 * pt2.X;
			double AY = SY1 + SY2 + 2 * pt1.Y - 2 * pt2.Y;
			double BX = -2 * SX1 - SX2 - 3 * pt1.X + 3 * pt2.X;
			double BY = -2 * SY1 - SY2 - 3 * pt1.Y + 3 * pt2.Y;

			double CX = SX1;
			double CY = SY1;
			double DX = pt1.X;
			double DY = pt1.Y;

			int num = (int)((Math.Abs(pt1.X - pt2.X) + Math.Abs(pt1.Y - pt2.Y)) / tolerance);

			// Notice begins at 1 so excludes the first point (which is just pt1)
			for (int i = 1; i < num; i++)
			{
				double t = (double)i / (num - 1);
				PointF pt = new PointF( (float)(AX * t * t * t + BX * t * t + CX * t + DX),
									(float)(AY * t * t * t + BY * t * t + CY * t + DY));
				points.Add(pt);
			}
		}

	}
}
